--------Les Secrets de Gertrude--------
A 4am crack                  2026-05-05

Name: Les Secrets de Gertrude
  ("Gertrude's Secrets" in French)
Version: 1.2
Genre: educational
Year: 1985
Credits: Leslie Grimm, Teri Perl,
  Warren Robinett, The Learning Company
Publisher: Vifi International
Platform: Apple ][+ (48K)
Media: 5.25-inch disk
Sides: 1
OS: DOS 3.3
Previous cracks: none

 ___________________________________
/\                                  \
\_| "Accepting that programs will   |
  | not only have to be designed    |
  | and produced, but also modified |
  | so as to cater for changing     |
  | demands, it is concluded that   |
  | the proper, primary aim of      |
  | programming, is not to produce  |
  | programs, but to have the       |
  | programmers build theories of   |
  | the manner in which the         |
  | problems at hand are solved by  |
  | program execution."             |
  |                                 |
  | -Programming as Theory Building |
  |                Peter Naur, 1985 |
  |   ______________________________|_
   \_/________________________________/


...............CHAPTER 0...............
 IN WHICH VARIOUS AUTOMATED TOOLS FAIL
          IN INTERESTING WAYS

COPYA
  no read errors, but copy crashes
  during boot

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Copy ][+ nibble editor
  nothing unusual

Disk Edit
- track $00 has a mostly DOS 3.3-shaped
  bootloader
- the boot sector has some patches to
  mess up the reset vector and set the
  Applesoft RUN flag ($D6)
- startup program is BOOT, according to
  track $01, sector $09
- disk catalog is on track $10 instead
  of track $11

Why didn't anything work?
  presumably there is some runtime
  protection check that triggers during
  boot (likely in the startup program,
  because the boot did get far enough
  to display the usual "]" prompt
  indicating it had fully loaded DOS
  and passed control to the startup
  program)

Next steps:

  1. find runtime protection check
  2. disable it
  3. declare victory (*)

(*) take a nap


...............CHAPTER 1...............
 IN WHICH WE TEST OUR ERROR CONDITIONS

Booting the non-working copy and
pressing [Ctrl-C] breaks to a BASIC
prompt. I noticed earlier that the boot
sector had set the Applesoft RUN flag
($D6), which would make any command I
try to type at this prompt RUN the
current program instead. But it appears
that DOS has reset this flag for us,
and whoever wrote the patch did not
realize that DOS would do that.

Always test your error conditions!

Anyway, I have full access to the disk.

]CATALOG

?SYNTAX ERROR
]

Well, almost full access. It seems that
someone has disabled or renamed some of
the DOS commands.

"Beneath Apple DOS" p. 8-14 lists the
CATALOG entry point at $A56E (42350),
so let's try calling that directly.

]CALL 42350

DISK VOLUME 254

 A 002 HELLO
 B 055 OLD SECRET WORLD
 B 009 SECRETSGOOSE.O
 B 034 ADV #108
 B 005 OLD SECRETFONT
 B 002 SECRETSRANDOM.O
 B 015 SECRETSHELLO&TITLER.OBJ0
 B 004 SECRETSHELLO.O
 B 026 SECRETCODE.O
 B 002 FRESHSTART.O
 B 055 OLD SECRET WORLD.IIE
 B 033 SECRETLOGO
 B 005 SECRETFONT
 B 055 SECRET WORLD
 B 055 SECRET WORLD.IIE
 B 033 VAF.C
 B 008 BLOC
 A 003 BOOT

]LIST

 1  POKE 214,255: CALL 2128: IF  PEEK
     (8) <  > 16 THEN  PRINT  CHR$
     (4)"RUN HELLO"
 2  PRINT "ENLEVER PROTECTION"

This startup program is named BOOT (as
I saw earlier in the sector editor),
and its entire purpose is to call the
copy protection routine then run the
real startup program named HELLO.

But where is the protection routine?
This is one of those BASIC programs
that has been patched to include some
assembly language code after the last
instruction. By default, BASIC programs
load in memory at $0801. The CALL 2128
instruction in line 1 will call an
embedded assembly language routine at
$0850, which was loaded as part of this
"BASIC" program but is otherwise
invisible in the listing.

Also of note: the POKE 214,255 in line
1 is setting the Applesoft RUN flag.
Again, because this is the same flag
they set in the boot sector, but then
DOS cleared it. Pressing [Ctrl-C]
during boot will break to the BASIC
prompt before any BASIC code in the
startup program executes. So they set
this flag both too early and too late
to prevent what I am currently doing.

]RENAME BOOT,OLD BOOT
]RENAME HELLO,BOOT
]PR#6
...game loads successfully...

Now that that's sorted, let's back up
and look at the protection routine we
just bypassed.


...............CHAPTER 2...............
  IN WHICH WE LOOK AT THE PROTECTION
       ROUTINE WE JUST BYPASSED

Starting over with a fresh non-working
copy, I press [Ctrl-C] to break to the
BASIC prompt again. But this time,
let's go exploring.

]CALL -151

*850L

; The first thing we do is set the
; address ($08) that the BASIC program
; checks for success. Unclear if this
; is some sort of tamper check ("ensure
; the protection routine was actually
; called") or a coincidence.
0850-   A9 02       LDA   #$02
0852-   85 08       STA   $08

; take that value (not entirely sure
; what it is yet) and get two other
; values out of it, and store them
0854-   A5 08       LDA   $08
0856-   AA          TAX
0857-   4A          LSR
0858-   09 AA       ORA   #$AA
085A-   85 FE       STA   $FE    ; #$AB
085C-   8A          TXA
085D-   09 AA       ORA   #$AA
085F-   85 FF       STA   $FF    ; #$AA

; get address of RWTS parameter table
0861-   20 E3 03    JSR   $03E3
0864-   84 06       STY   $06
0866-   85 07       STA   $07

; take that value in $08 and store it
; in the RWTS parameter table as the
; track number
0868-   A5 08       LDA   $08
086A-   A0 04       LDY   #$04
086C-   91 06       STA   ($06),Y

; #$00=seek command
086E-   A9 00       LDA   #$00
0870-   A0 0C       LDY   #$0C
0872-   91 06       STA   ($06),Y

; execute the track seek
0874-   20 E3 03    JSR   $03E3
0877-   20 D9 03    JSR   $03D9

Now we're on track 2.

; turn on disk motor manually
; (this is never not suspicious)
087A-   BD 89 C0    LDA   $C089,X
087D-   A9 00       LDA   #$00
087F-   85 48       STA   $48

; get drive (x16)
0881-   A0 01       LDY   #$01
0883-   B1 06       LDA   ($06),Y
0885-   AA          TAX
0886-   BD 8E C0    LDA   $C08E,X

; look for nibble sequence
; #$FF then #$FF then not #$FF
0889-   A0 00       LDY   #$00
088B-   BD 8C C0    LDA   $C08C,X
088E-   10 FB       BPL   $088B
0890-   C9 FF       CMP   #$FF
0892-   D0 F7       BNE   $088B
0894-   BD 8C C0    LDA   $C08C,X
0897-   10 FB       BPL   $0894
0899-   C9 FF       CMP   #$FF
089B-   D0 EE       BNE   $088B
089D-   BD 8C C0    LDA   $C08C,X
08A0-   10 FB       BPL   $089D
08A2-   C9 FF       CMP   #$FF
08A4-   F0 F7       BEQ   $089D

; start counting
08A6-   A9 00       LDA   #$00
08A8-   85 06       STA   $06
08AA-   85 07       STA   $07

; next two nibbles must match what we
; stored in addresses $FE and $FF
; (#$AB and #$AA, respectively)
; otherwise start count over from $08A6
08AC-   BD 8C C0    LDA   $C08C,X
08AF-   10 FB       BPL   $08AC
08B1-   E6 06       INC   $06
08B3-   D0 02       BNE   $08B7
08B5-   E6 07       INC   $07
08B7-   C5 FE       CMP   $FE
08B9-   D0 EB       BNE   $08A6
08BB-   BD 8C C0    LDA   $C08C,X
08BE-   10 FB       BPL   $08BB
08C0-   E6 06       INC   $06
08C2-   D0 02       BNE   $08C6
08C4-   E6 07       INC   $07
08C6-   C5 FF       CMP   $FF
08C8-   D0 DC       BNE   $08A6

; next two nibbles must be #$AF #$AF
; otherwise start count over from $08A6
08CA-   BD 8C C0    LDA   $C08C,X
08CD-   10 FB       BPL   $08CA
08CF-   E6 06       INC   $06
08D1-   D0 02       BNE   $08D5
08D3-   E6 07       INC   $07
08D5-   C9 AF       CMP   #$AF
08D7-   D0 CD       BNE   $08A6
08D9-   BD 8C C0    LDA   $C08C,X
08DC-   10 FB       BPL   $08D9
08DE-   E6 06       INC   $06
08E0-   D0 02       BNE   $08E4
08E2-   E6 07       INC   $07
08E4-   C9 AF       CMP   #$AF
08E6-   D0 BE       BNE   $08A6

; count until we find a 4-nibble
; sequence containing
; - the value from $FE
; - the value from $FF
; - #$AA
; - #$AA again
08E8-   BD 8C C0    LDA   $C08C,X
08EB-   10 FB       BPL   $08E8
08ED-   E6 06       INC   $06
08EF-   D0 02       BNE   $08F3
08F1-   E6 07       INC   $07
08F3-   C5 FE       CMP   $FE
08F5-   D0 F1       BNE   $08E8
08F7-   BD 8C C0    LDA   $C08C,X
08FA-   10 FB       BPL   $08F7
08FC-   E6 06       INC   $06
08FE-   D0 02       BNE   $0902
0900-   E6 07       INC   $07
0902-   C5 FF       CMP   $FF
0904-   D0 E2       BNE   $08E8
0906-   BD 8C C0    LDA   $C08C,X
0909-   10 FB       BPL   $0906
090B-   E6 06       INC   $06
090D-   D0 02       BNE   $0911
090F-   E6 07       INC   $07
0911-   C9 AA       CMP   #$AA
0913-   D0 D3       BNE   $08E8
0915-   BD 8C C0    LDA   $C08C,X
0918-   10 FB       BPL   $0915
091A-   E6 06       INC   $06
091C-   D0 02       BNE   $0920
091E-   E6 07       INC   $07
0920-   C9 AA       CMP   #$AA
0922-   D0 C4       BNE   $08E8

; check the low byte of the counter
; against an unused (but suspiciously
; uninitialized) address in page 3,
; then store it there
0924-   A4 08       LDY   $08
0926-   A5 06       LDA   $06
0928-   D9 00 03    CMP   $0300,Y
092B-   99 00 03    STA   $0300,Y
092E-   F0 03       BEQ   $0933

; loop back if the counter didn't match
0930-   4C 61 08    JMP   $0861

; otherwise decrement the track number
; and do everything all over again for
; track 1 and track 0
0933-   88          DEY
0934-   84 08       STY   $08
0936-   C0 FF       CPY   #$FF
0938-   F0 03       BEQ   $093D
093A-   4C 54 08    JMP   $0854

We start by looking for a 4-nibble
sequence, #$AB #$AA #$AF #$AF. These
nibbles are part of the address field
for sector $F. #$AB #$AA is the
4-and-4-encoded value of the track
number (2), and #$AF #$AF is the
encoded value of the sector ($F).

Then we start counting until we find
another 4-nibble sequence, #$AB #$AA
#$AA #$AA, which is part of the address
field for sector $0. We actually do the
count twice. The first time sets the
value at $300,Y, and the second time
verifies that it got the same nibble
count again. It loops until it gets a
consistent value twice in a row.

That's the heart of the protection: a
nibble count between two sectors. But
not just any two sectors -- two sectors
on opposite sides of the track seam,
the filler at the end of the track
after all the sector data. Bit copiers
have an option to try to preserve the
length of a track exactly; it's not an
easy task for a variety of reasons, and
the way they do it may end up altering
this particular count anyway. (They may
decide to add or subtract nibbles
elsewhere on the track, between other
sectors.) And they would have to get it
exactly right on three separate tracks.

Continuing from $0933, we decrement the
track number (in address $08) and jump
back to $0854, which I now understand
is calculating the 4-and-4-encoded
value of the track:

0854-   A5 08       LDA   $08    ; now
0856-   AA          TAX          ; #$01
0857-   4A          LSR
0858-   09 AA       ORA   #$AA   ; now
085A-   85 FE       STA   $FE    ; #$AA
085C-   8A          TXA
085D-   09 AA       ORA   #$AA   ; now
085F-   85 FF       STA   $FF    ; #$AB

Now we do the nibble count all over
again, but on track 1, then on track 0.
Then the decrement at $0933 finally
goes negative, and we branch forward to
$093D for the dénouement.

; check each nibble count (low byte
; only) against the master table at
; $A990, giving a 1-nibble margin
093D-   EA          NOP
093E-   EA          NOP
093F-   A0 00       LDY   #$00
0941-   B9 90 A9    LDA   $A990,Y
0944-   38          SEC
0945-   F9 00 03    SBC   $0300,Y
0948-   10 03       BPL   $094D
094A-   38          SEC
094B-   E9 FF       SBC   #$FF
094D-   C9 01       CMP   #$01
094F-   B0 07       BCS   $0958
0951-   C8          INY
0952-   C0 03       CPY   #$03
0954-   D0 EB       BNE   $0941

; if all nibble counts match, the
; protection check has passed, so
; branch forward to the success path
0956-   F0 3E       BEQ   $0996

; if the nibble counts do not match,
; check if the master table is even
; initialized
0958-   A0 00       LDY   #$00
095A-   B9 90 A9    LDA   $A990,Y

; #$01 in the master table is treated
; specially
095D-   C9 01       CMP   #$01

; otherwise, yeah, this is a protection
; failure, off to The Badlands with you
095F-   D0 50       BNE   $09B1

What comes next will blow your socks
off. Unless you're not wearing socks,
in which case, I guess it won't.


...............CHAPTER 3...............
   IN WHICH THINGS ESCALATE QUICKLY

The current situation is that the
protection check has ostensibly failed.
It performed nibble counts on tracks 2,
1, and 0, but they do not match the
"master" values at $A990. But for good
reason! Those master values have not
been initialized. They are all set to
#$01.

; there are #$01s in the master table,
; so treat the current nibble counts as
; the correct values
0961-   B9 00 03    LDA   $0300,Y
0964-   99 90 A9    STA   $A990,Y
0967-   C8          INY
0968-   C0 03       CPY   #$03
096A-   D0 EE       BNE   $095A

; and write them to disk
; ($A900..A9FF to T01,S08)
096C-   20 E3 03    JSR   $03E3
096F-   84 06       STY   $06
0971-   85 07       STA   $07
0973-   A9 01       LDA   #$01     ;T01
0975-   A0 04       LDY   #$04
0977-   91 06       STA   ($06),Y
0979-   A9 08       LDA   #$08     ;S08
097B-   A0 05       LDY   #$05
097D-   91 06       STA   ($06),Y
097F-   A9 00       LDA   #$00   ;$A900
0981-   A0 08       LDY   #$08
0983-   91 06       STA   ($06),Y
0985-   A9 A9       LDA   #$A9
0987-   C8          INY
0988-   91 06       STA   ($06),Y
098A-   A9 02       LDA   #$02   ;write
098C-   A0 0C       LDY   #$0C
098E-   91 06       STA   ($06),Y
0990-   A9 00       LDA   #$00
0992-   A0 03       LDY   #$03
0994-   91 06       STA   ($06),Y

; success path continues here (but
; without any changes to the RWTS
; parameter table, so it'll just be
; another seek)
0996-   20 E3 03    JSR   $03E3
0999-   20 D9 03    JSR   $03D9

; get the RWTS error code, if any, and
; store it in $08 for the BASIC caller
; to check
099C-   20 E3 03    JSR   $03E3
099F-   84 06       STY   $06
09A1-   85 07       STA   $07
09A3-   A0 0D       LDY   #$0D
09A5-   B1 06       LDA   ($06),Y
09A7-   85 08       STA   $08

; turn off the drive motor and return
09A9-   BD 88 C0    LDA   $C088,X
09AC-   A9 00       LDA   #$00
09AE-   85 48       STA   $48
09B0-   60          RTS

This is very cool, because it decouples
the copy protection routine from the
disk mastering process. You can create
your master disks however you want. Put
#$01 #$01 #$01 in the "master" nibble
count table at $A990 (T01,S08), then
boot the disk one time. It will perform
the nibble count on tracks 2, 1, and 0,
then write them to disk as the correct
values. Every future boot, it will use
those values to confirm that the disk
is original.

; The Badlands are here -- format
; track 0 and crash (no, really)
09B1-   20 E3 03    JSR   $03E3
09B4-   84 06       STY   $06
09B6-   85 07       STA   $07
09B8-   A9 04       LDA   #$04  ;format
09BA-   A0 0C       LDY   #$0C  ;cmd
09BC-   91 06       STA   ($06),Y
09BE-   20 E3 03    JSR   $03E3
09C1-   20 D9 03    JSR   $03D9 ;do it
09C4-   00          BRK         ;crash

Well that escalated quickly.

Assuming we do not want to rename the
HELLO program to bypass this protection
at the file level, we can modify this
protection routine to return almost as
soon as it starts. The BASIC caller
will check the value of address $08,
and lo! The first thing the protection
check does is set this address to an
acceptable value (for unrelated
reasons, but I'll take it).

0850-   A9 02       LDA   #$02
0852-   85 08       STA   $08

So the next instruction can be an "RTS"
and the protection check will always
pass.

A quick search on disk finds the
protection check on track $19, embedded
within the BASIC program named BOOT, as
explained earlier.

[Disk Edit]
  [search for hex]
    [BD 89 C0]

                 --v--

------------- DISK SEARCH -------------

$00/$07-$4F   $19/$07-$7B


             PRESS [RETURN]

                 --^--

T19,S07,$55: A5 -> 60

Quod erat liberandum.


A 4am crack                    No. 3344
------------------EOF------------------
